gridview: Add a focus tracker
authorBenjamin Otte <otte@redhat.com>
Sun, 20 Oct 2019 18:09:24 +0000 (20:09 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Sat, 30 May 2020 23:26:46 +0000 (19:26 -0400)
... and use that to properly update selections when moving around with
the arrow keys.

gtk/gtkgridview.c

index 40ff8cddd9f456e9ee20887b7a686694a3b50f66..5e2d0791b78ed2780c2553533b3e955699bc91ac 100644 (file)
@@ -25,6 +25,7 @@
 #include "gtkintl.h"
 #include "gtklistitemfactory.h"
 #include "gtklistitemmanagerprivate.h"
+#include "gtkmain.h"
 #include "gtkorientableprivate.h"
 #include "gtkprivate.h"
 #include "gtkscrollable.h"
@@ -78,6 +79,8 @@ struct _GtkGridView
   guint anchor_ystart : 1;
   /* the last item that was selected - basically the location to extend selections from */
   GtkListItemTracker *selected;
+  /* the item that has input focus */
+  GtkListItemTracker *focus;
 };
 
 struct _Cell
@@ -396,6 +399,66 @@ gtk_grid_view_select_item (GtkGridView *self,
     }
 }
 
+static gboolean
+gtk_grid_view_focus (GtkWidget        *widget,
+                     GtkDirectionType  direction)
+{
+  GtkGridView *self = GTK_GRID_VIEW (widget);
+  GtkWidget *old_focus_child, *new_focus_child;
+
+  old_focus_child = gtk_widget_get_focus_child (widget);
+
+  if (old_focus_child == NULL &&
+      (direction == GTK_DIR_TAB_FORWARD || direction == GTK_DIR_TAB_BACKWARD))
+    {
+      Cell *cell;
+      guint pos;
+
+      /* When tabbing into the listview, don't focus the first/last item,
+       * but keep the previously focused item
+       */
+      pos = gtk_list_item_tracker_get_position (self->item_manager, self->focus);
+      cell = gtk_list_item_manager_get_nth (self->item_manager, pos, NULL);
+      if (cell && gtk_widget_grab_focus (cell->parent.widget))
+        goto moved_focus;
+    }
+
+  if (!GTK_WIDGET_CLASS (gtk_grid_view_parent_class)->focus (widget, direction))
+    return FALSE;
+
+moved_focus:
+  new_focus_child = gtk_widget_get_focus_child (widget);
+
+  if (old_focus_child != new_focus_child &&
+      GTK_IS_LIST_ITEM (new_focus_child))
+    {
+      gboolean extend = FALSE, modify = FALSE;
+
+      if (old_focus_child)
+        {
+          GdkSeat *seat = gdk_display_get_default_seat (gtk_widget_get_display (widget));
+          if (seat)
+            {
+              GdkDevice *keyboard = gdk_seat_get_keyboard (seat);
+              if (keyboard)
+                {
+                  GdkModifierType state = gdk_device_get_modifier_state (keyboard);
+
+                  extend = (state & GDK_SHIFT_MASK) != 0;
+                  modify = (state & GDK_CONTROL_MASK) != 0;
+                }
+            }
+        }
+
+      gtk_grid_view_select_item (self,
+                                 gtk_list_item_get_position (GTK_LIST_ITEM (new_focus_child)),
+                                 modify,
+                                 extend);
+    }
+
+  return TRUE;
+}
+
 static gboolean
 gtk_grid_view_adjustment_is_flipped (GtkGridView    *self,
                                      GtkOrientation  orientation)
@@ -1041,6 +1104,11 @@ gtk_grid_view_dispose (GObject *object)
       gtk_list_item_tracker_free (self->item_manager, self->selected);
       self->selected = NULL;
     }
+  if (self->focus)
+    {
+      gtk_list_item_tracker_free (self->item_manager, self->focus);
+      self->focus = NULL;
+    }
   g_clear_object (&self->item_manager);
 
   G_OBJECT_CLASS (gtk_grid_view_parent_class)->dispose (object);
@@ -1275,6 +1343,27 @@ gtk_grid_view_compute_scroll_align (GtkGridView   *self,
     }
 }
 
+static void
+gtk_grid_view_update_focus_tracker (GtkGridView *self)
+{
+  GtkWidget *focus_child;
+  guint pos;
+
+  focus_child = gtk_widget_get_focus_child (GTK_WIDGET (self));
+  if (!GTK_IS_LIST_ITEM (focus_child))
+    return;
+
+  pos = gtk_list_item_get_position (GTK_LIST_ITEM (focus_child));
+  if (pos != gtk_list_item_tracker_get_position (self->item_manager, self->focus))
+    {
+      gtk_list_item_tracker_set_position (self->item_manager,
+                                          self->focus,
+                                          pos,
+                                          self->max_columns,
+                                          self->max_columns);
+    }
+}
+
 static void
 gtk_grid_view_scroll_to_item (GtkWidget  *widget,
                               const char *action_name,
@@ -1312,6 +1401,14 @@ gtk_grid_view_scroll_to_item (GtkWidget  *widget,
                                       &xalign, &xstart);
 
   gtk_grid_view_set_anchor (self, pos, xalign, xstart, yalign, ystart);
+
+  /* HACK HACK HACK
+   *
+   * GTK has no way to track the focused child. But we now that when a listitem
+   * gets focus, it calls this action. So we update our focus tracker from here
+   * because it's the closest we can get to accurate tracking.
+   */
+  gtk_grid_view_update_focus_tracker (self);
 }
 
 static void
@@ -1339,6 +1436,7 @@ gtk_grid_view_class_init (GtkGridViewClass *klass)
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   gpointer iface;
 
+  widget_class->focus = gtk_grid_view_focus;
   widget_class->measure = gtk_grid_view_measure;
   widget_class->size_allocate = gtk_grid_view_size_allocate;
 
@@ -1541,6 +1639,7 @@ gtk_grid_view_init (GtkGridView *self)
   self->anchor_xstart = TRUE;
   self->anchor_ystart = TRUE;
   self->selected = gtk_list_item_tracker_new (self->item_manager);
+  self->focus = gtk_list_item_tracker_new (self->item_manager);
 
   self->min_columns = 1;
   self->max_columns = DEFAULT_MAX_COLUMNS;
@@ -1745,6 +1844,7 @@ gtk_grid_view_set_max_columns (GtkGridView *self,
                             self->anchor_xstart,
                             self->anchor_yalign,
                             self->anchor_ystart);
+  gtk_grid_view_update_focus_tracker (self);
 
   gtk_widget_queue_resize (GTK_WIDGET (self));